#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <math.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/prctl.h>

#include <netinet/in.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>

#include <net/route.h>
#include <time.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

#include "hi_ext_util.h"
#include "plug_stream.h"

#ifdef PLUG_STREAM_LIBMPEG
#include <map>
#include "mpeg-ts.h"
#include "mpeg-ts-proto.h"
#else
extern "C" 
{
#include "libavutil/opt.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
#endif /* PLUG_STREAM_LIBMPEG */

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* End of #ifdef __cplusplus */

typedef struct tagPLUG_STREAM_CTX
{
    HI_BOOL bInit;
    pthread_mutex_t writeLock;

#ifdef PLUG_STREAM_LIBMPEG
    HI_VOID *pTsMuxer;
    HI_S32 s32VideoStreamId;
#else
    HI_S64 alastpts;
    HI_S64 vlastpts;
    AVFormatContext* pstAvCtx;
    
    HI_U8 *otsbuf;
    HI_U32 *pu32Header;
    HI_U8 *pBuff;
    HI_U8 u8Buf[PLUG_STREAM_PACKET_SIZE + 4];
    HI_S32 pBuffIndx;
#endif

    HI_HANDLE socket;
    struct sockaddr_in ipaddr;
    HI_BOOL connected;
} PLUG_STREAM_CTX;

static PLUG_STREAM_CTX s_stSteamCtx = {0};

static HI_S32 STREAM_ReConnect(HI_VOID)
{
    HI_S32 s32Ret = HI_SUCCESS;
    PLUG_COMMON_DEV_INFO_S stInfo = {0};
    
    s32Ret = PLUG_COMMON_GetDeviceInfo(&stInfo);
    if (HI_SUCCESS != s32Ret)
    {
        LOGE("PLUG_COMMON_GetDeviceInfo error!\n");
    }

    s_stSteamCtx.ipaddr.sin_family = AF_INET;
    s_stSteamCtx.ipaddr.sin_port = htons(stInfo.s32StreamPort);
    s_stSteamCtx.ipaddr.sin_addr.s_addr = inet_addr(stInfo.azStreamIp);
    
    s_stSteamCtx.socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if(s_stSteamCtx.socket < 0)
    {
        LOGE("socket failed!\n");
        return HI_FAILURE;
    }
    
    HI_S32 val = 1;
    HI_S32 error = 0;
    ioctl(s_stSteamCtx.socket,FIONBIO, &val);
    
    s32Ret = connect(s_stSteamCtx.socket ,(struct sockaddr *)&s_stSteamCtx.ipaddr,sizeof(struct sockaddr));
    if (HI_FAILURE == s32Ret)
    {
        if (errno == EINPROGRESS){
            struct timeval tv; 
            fd_set writefds; 
            tv.tv_sec = 5; 
            tv.tv_usec = 0; 
            FD_ZERO(&writefds); 
            FD_SET(s_stSteamCtx.socket, &writefds); 
            if(select(s_stSteamCtx.socket + 1, NULL, &writefds,NULL,&tv)>0){ 
                int len=sizeof(int);
                HI_S32 socketoptlevel = SOL_SOCKET;
                getsockopt(s_stSteamCtx.socket,socketoptlevel, SO_ERROR,&error,&len);
                if(error==0) {
                    s_stSteamCtx.connected = HI_TRUE; 
                }
                else {
                    s_stSteamCtx.connected = HI_FALSE; 
                }
            }
            else {
                s_stSteamCtx.connected = HI_FALSE;//timeout or error happen 
            }
        }else  {
            s_stSteamCtx.connected = HI_FALSE; 
        }
    }
    else
    {
        s_stSteamCtx.connected = HI_TRUE;
    }
    
    if(s_stSteamCtx.connected)
    {
        HI_CHAR buff[200];
        
        val = 0;
        ioctl(s_stSteamCtx.socket, FIONBIO, &val);
        sprintf(buff, "deviceCode=%s&uid=%s\r\n", stInfo.azDeviceId, "[]");

        LOGI("buff = %s\n",buff);
        send(s_stSteamCtx.socket,buff,strlen(buff),0);
    }
    else
    {
        close(s_stSteamCtx.socket);
    }
    
    return HI_SUCCESS;
}

static int STREAM_SocketSend(HI_CHAR* pazBuf, HI_S32 s32BufLen)
{
    HI_S32 write_size = 0;
    HI_S32 len = s32BufLen;
    HI_S32 s32Ret = HI_SUCCESS;
    
    if(!s_stSteamCtx.connected)
    {
        /* 创建socket连接 */
        s32Ret = STREAM_ReConnect();
        if (HI_SUCCESS != s32Ret)
        {
            LOGE("STREAM_ReConnect fail!\n");
            return HI_FAILURE;
        }
    }
    
    if(!s_stSteamCtx.connected)
    {
        return s32BufLen;
    }
    
    /* socket 发送ts流数据 */
    do {
        
        s32Ret = send(s_stSteamCtx.socket, pazBuf,len,0);
        if(s32Ret<=0)
        {
            printf("send buff fail:%d, %d\n", s32Ret, len);
            if(errno != EINPROGRESS && errno != 11)
            {
                printf("write fail errno:%d\n", errno);
                close(s_stSteamCtx.socket);
                s_stSteamCtx.connected = HI_FALSE;
                break;
            }
        } else {
            write_size += s32Ret;
            pazBuf += s32Ret;
            len -= s32Ret;
        }
        
    } while(write_size < s32BufLen);
    
    return HI_SUCCESS;
}


#ifdef PLUG_STREAM_LIBMPEG
static int STREAM_TS_Stream(void* ts, int codecid)
{
    static std::map<int, int> streams;
    std::map<int, int>::const_iterator it = streams.find(codecid);
    if (streams.end() != it)
        return it->second;

    int i = mpeg_ts_add_stream(ts, codecid, NULL, 0);
    streams[codecid] = i;
    return i;
}

static HI_VOID *STREAM_TsAlloc(void *param, size_t bytes)
{
    static char s_buffer[188];
    assert(bytes <= sizeof(s_buffer));
    return s_buffer;
}

static HI_VOID STREAM_TsFree(void *param, void* packet)
{
    return ;
}

static HI_S32 STREAM_TsSend(void* param, const void* packet, size_t bytes)
{
    HI_S32 s32Ret = HI_SUCCESS;
    
    s32Ret = STREAM_SocketSend((HI_CHAR*)packet, bytes);
    if (s32Ret != HI_SUCCESS)
    {
        LOGE("STREAM_SocketSend send [%d]failed!\n", s32Ret);
        return HI_FAILURE;
    }
    
    return HI_SUCCESS;
}
#else

static HI_S32 STREAM_CreateAv()
{
    AVStream* avStream = avformat_new_stream(s_stSteamCtx.pstAvCtx, NULL);\
    avStream->id = 0;\
    avStream->codecpar->width  = PLUG_STREAM_WIDTH;\
    avStream->codecpar->height = PLUG_STREAM_HEIGHT;\
    avStream->codecpar->codec_type =  AVMEDIA_TYPE_VIDEO;\
    avStream->codecpar->codec_id   = AV_CODEC_ID_H264;\
    avStream->codecpar->format        = AV_PIX_FMT_YUVJ420P;\
    avStream->codecpar->bit_rate   = 100000;\
    avStream->time_base = (AVRational){1, 90000};\
    avStream->r_frame_rate = (AVRational){30, 1};\
    avStream->avg_frame_rate = (AVRational){30, 1};\
    
    AVStream * avStream1 = avformat_new_stream(s_stSteamCtx.pstAvCtx, NULL);\
    avStream1->id = 1;
    avStream1->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; \
    avStream1->codecpar->codec_id = AV_CODEC_ID_AAC;\
    avStream1->codecpar->format = AV_SAMPLE_FMT_FLTP;\
    avStream1->codecpar->sample_rate = 48000;\
    avStream1->codecpar->bit_rate    = 24000;\
    avStream1->nb_frames = 1;\
    avStream1->codecpar->channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); \
    avStream1->time_base = (AVRational){ 1, 90000 }; \

    return HI_SUCCESS;
}

static HI_S32 STREAM_TsSend(HI_VOID *opaque, HI_U8 *buf, HI_S32 buf_size)
{
    HI_S32 s32Ret = HI_SUCCESS;
    static HI_U8 u8EventType = 0b0100;

    if(buf_size + s_stSteamCtx.pBuffIndx >= PLUG_STREAM_PACKET_SIZE)
    {
        int len = PLUG_STREAM_PACKET_SIZE - s_stSteamCtx.pBuffIndx;
        memcpy(s_stSteamCtx.pBuff + s_stSteamCtx.pBuffIndx, buf, len);
        s_stSteamCtx.pBuffIndx = 0;
        
        *s_stSteamCtx.pu32Header = (*s_stSteamCtx.pu32Header) & 0xffffff00 | u8EventType;
        s32Ret = STREAM_SocketSend(s_stSteamCtx.u8Buf, PLUG_STREAM_PACKET_SIZE + 4);
        if (s32Ret != HI_SUCCESS)
        {
            LOGE("STREAM_SocketSend send [%d]failed!\n", s32Ret);
        }
        
        if(buf_size > len)
        {
            memcpy(s_stSteamCtx.pBuff + s_stSteamCtx.pBuffIndx, buf + len, buf_size - len); 
            s_stSteamCtx.pBuffIndx = buf_size - len;
        }
    }
    else
    {
        memcpy(s_stSteamCtx.pBuff + s_stSteamCtx.pBuffIndx, buf , buf_size);
        s_stSteamCtx.pBuffIndx += buf_size;
    }
    
    return HI_SUCCESS;
}

static int64_t STREAM_TsSeek(HI_VOID *opaque, int64_t offset, HI_S32 whence)
{
    return offset;
}

#endif

static HI_S32 STREAM_Init(HI_VOID)
{
    HI_S32 s32Ret = HI_SUCCESS;
#ifdef PLUG_STREAM_LIBMPEG
    struct mpeg_ts_func_t tshandler;
    tshandler.alloc = STREAM_TsAlloc;
    tshandler.write = STREAM_TsSend;
    tshandler.free = STREAM_TsFree;

    /* 创建ts复用器 */
    s_stSteamCtx.pTsMuxer = mpeg_ts_create(&tshandler, &s_stSteamCtx);
    if (HI_NULL == s_stSteamCtx.pTsMuxer)
    {
        LOGE("mpeg_ts_create fail!\n");
        return HI_FAILURE;
    }
    
    s_stSteamCtx.s32VideoStreamId = STREAM_TS_Stream(s_stSteamCtx.pTsMuxer, PSI_STREAM_H264);
#else
    avformat_alloc_output_context2((AVFormatContext **)&s_stSteamCtx.pstAvCtx, NULL, "mpegts", NULL); 
    STREAM_CreateAv();
    
    s_stSteamCtx.otsbuf = (HI_U8 *)malloc(PLUG_STREAM_PACKET_SIZE);
    s_stSteamCtx.pstAvCtx->pb = avio_alloc_context(s_stSteamCtx.otsbuf, PLUG_STREAM_PACKET_SIZE,\
            1, HI_NULL, HI_NULL, STREAM_TsSend, STREAM_TsSeek);

    HI_CHAR azUrl[128];
    PLUG_COMMON_DEV_INFO_S stInfo = {0};
    
    s32Ret = PLUG_COMMON_GetDeviceInfo(&stInfo);
    if (HI_SUCCESS != s32Ret)
    {
        LOGE("PLUG_COMMON_GetDeviceInfo error!\n");
    }
    
    snprintf(azUrl, sizeof(azUrl), "%s:%d/%s", stInfo.azStreamIp, stInfo.s32StreamPort, stInfo.azDeviceId);
    av_dump_format(s_stSteamCtx.pstAvCtx, 0, azUrl, 1);
    s32Ret = avformat_write_header(s_stSteamCtx.pstAvCtx, HI_NULL);
    if (s32Ret != HI_SUCCESS)
    {
        LOGE("avformat_write_header failed![%d]\n", s32Ret);
    }
    
    s_stSteamCtx.pu32Header = (HI_U32 *)s_stSteamCtx.u8Buf;
    s_stSteamCtx.pBuff = &s_stSteamCtx.u8Buf[4];
    s_stSteamCtx.pBuffIndx = 0;
#endif

    return HI_SUCCESS;
}

static HI_S32 STREAM_SendVideoFrame(const void* data, HI_S32 size, HI_BOOL bIsIframe, int64_t pts, int64_t dts)
{
    HI_S32 s32Ret = HI_SUCCESS;

#ifdef PLUG_STREAM_LIBMPEG
    s32Ret = mpeg_ts_write(s_stSteamCtx.pTsMuxer, s_stSteamCtx.s32VideoStreamId, \
        bIsIframe, pts, dts, data, size);
    if (s32Ret != 0)
    {
        LOGE("mpeg_ts_write failed!%d\n", s32Ret);
    }
#else

    AVPacket stAvPack;
    memset(&stAvPack, 0, sizeof(AVPacket));
    stAvPack.flags |= bIsIframe ? AV_PKT_FLAG_KEY : AV_PKT_FLAG_CORRUPT;
    stAvPack.stream_index = 0;/* video */
    stAvPack.data = (uint8_t *)data;
    stAvPack.size = size;
    uint64_t mpts = pts * 90 / 1000;
    if(mpts<=s_stSteamCtx.vlastpts)
    {
        mpts = s_stSteamCtx.vlastpts + 1;
    }
    
    stAvPack.pts = stAvPack.dts =  mpts ;
    stAvPack.duration = (mpts - s_stSteamCtx.vlastpts) ;
    s_stSteamCtx.vlastpts = mpts; 
    
    //printf("[Func]:%s [Line]:%d stAvPack.size:%d [%lld], pts[%lld]\n",__FUNCTION__, __LINE__, stAvPack.size, mpts, pts);

    stAvPack.pos=-1;
    s32Ret = av_interleaved_write_frame(s_stSteamCtx.pstAvCtx, &stAvPack);
    if (HI_SUCCESS != s32Ret)
    {
        LOGE("av_interleaved_write_frame failed![%d]\n", s32Ret);
    }

#endif

    return HI_SUCCESS;
}

HI_S32 STREAM_SendAudioFrame(       PLUG_COMMON_AI_DATA_INFO* pstAiData)
{
    HI_S32 s32Ret = HI_SUCCESS;
#ifndef PLUG_STREAM_LIBMPEG
    AVPacket stAvPack;
    HI_S32 u16len = 0;
    av_init_packet(&stAvPack);

    stAvPack.data = (uint8_t *)pstAiData->pu8Buff;
    stAvPack.size = pstAiData->s32Bufflen;
    
    stAvPack.stream_index = 1; /* audio */
    uint64_t mpts = pstAiData->u64Pts * 90 / 1000;
    if(mpts<=s_stSteamCtx.alastpts)
        mpts = s_stSteamCtx.alastpts + 1;
    stAvPack.pts = stAvPack.dts =  mpts;
    stAvPack.duration = mpts - s_stSteamCtx.vlastpts;    
    s_stSteamCtx.alastpts = mpts;
    stAvPack.duration = (stAvPack.pts - s_stSteamCtx.alastpts);
    stAvPack.pos=-1;
    s32Ret = av_interleaved_write_frame(s_stSteamCtx.pstAvCtx, &stAvPack);
    if (HI_SUCCESS != s32Ret)
    {
        LOGE("av_interleaved_write_frame failed![%d]\n", s32Ret);
    }
    
    av_packet_unref(&stAvPack);

    return HI_SUCCESS;
#endif

}


static HI_S32 STREAM_Deinit(HI_VOID)
{
#ifdef PLUG_STREAM_LIBMPEG
#else
    free(s_stSteamCtx.otsbuf);
#endif

    return HI_SUCCESS;
}

HI_S32 PLUG_STREAM_Init(HI_VOID)
{
    HI_S32 s32Ret = HI_SUCCESS;

    if (s_stSteamCtx.bInit)
    {
        LOGE("PLUG_STREAM_Init has init\n");
        return HI_FAILURE;
    }
    
    /* 创建初始化stream */
    s32Ret = STREAM_Init();
    if (HI_SUCCESS != s32Ret)
    {
        LOGE("STREAM_Init fail!\n");
        return HI_FAILURE;
    }

    /* 创建socket连接 */
    s32Ret = STREAM_ReConnect();
    if (HI_SUCCESS != s32Ret)
    {
        LOGE("STREAM_ReConnect fail!\n");
        return HI_FAILURE;
    }
    
    pthread_mutex_init(&s_stSteamCtx.writeLock, NULL);
    
    s_stSteamCtx.bInit = HI_TRUE;
    return HI_SUCCESS;
}

HI_S32 PLUG_STREAM_SendVideoFrame(const void* data, HI_S32 size, HI_BOOL bIsIframe, int64_t pts, int64_t dts)
{
    HI_S32 s32Ret = HI_SUCCESS;
    
    if (s_stSteamCtx.bInit)
    {
        pthread_mutex_lock(&s_stSteamCtx.writeLock);
        s32Ret = STREAM_SendVideoFrame(data, size, bIsIframe, pts, dts);
        if (HI_SUCCESS != s32Ret)
        {
            LOGE("STREAM_SendVideoFrame fail! %d\n", s32Ret);
            pthread_mutex_unlock(&s_stSteamCtx.writeLock);
            return HI_FAILURE;
        }
        pthread_mutex_unlock(&s_stSteamCtx.writeLock);
    }
    
    return HI_SUCCESS;
}

HI_S32 PLUG_STREAM_SendAudioFrame(       PLUG_COMMON_AI_DATA_INFO* pstAiData)
{
    HI_S32 s32Ret = HI_SUCCESS;
    
    if (s_stSteamCtx.bInit)
    {
        pthread_mutex_lock(&s_stSteamCtx.writeLock);
        s32Ret = STREAM_SendAudioFrame(pstAiData);
        if (HI_SUCCESS != s32Ret)
        {
            LOGE("STREAM_SendAudioFrame fail! %d\n", s32Ret);
            pthread_mutex_unlock(&s_stSteamCtx.writeLock);
            return HI_FAILURE;
        }
        pthread_mutex_unlock(&s_stSteamCtx.writeLock);
    }
    
    return HI_SUCCESS;
}

HI_S32 PLUG_STREAM_Deinit(HI_VOID)
{
    HI_S32 s32Ret = HI_SUCCESS;

    if (!s_stSteamCtx.bInit)
    {
        LOGE("PLUG_STREAM_Init not init\n");
        return HI_FAILURE;
    }
    
    s_stSteamCtx.bInit = HI_FALSE;
    
    /* 去初始化流 */
    s32Ret = STREAM_Deinit();
    if (HI_SUCCESS != s32Ret)
    {
        LOGE("STREAM_Deinit fail!\n");
        return HI_FAILURE;
    }
    
    close(s_stSteamCtx.socket);
    s_stSteamCtx.connected = HI_FALSE;
    pthread_mutex_destroy(&s_stSteamCtx.writeLock);
    
    return HI_SUCCESS;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* End of #ifdef __cplusplus */

